package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"sort"
)

type PlayerStore interface {
	GetPlayerScore(name string) int
	ProcessWin(name string)
	GetLeague() League
}

type InMemoryPlayerStore struct {
	score map[string]int
}

type FileSystemStore struct {
	database *json.Encoder
	league   League
}

type PlayerServer struct {
	store  PlayerStore
	router *http.ServeMux
}

type Player struct {
	Name  string
	Score int
}

type League []Player

type Tape struct {
	file *os.File
}

func (t Tape) Write(p []byte) (n int, err error) {
	t.file.Truncate(0)
	t.file.Seek(0, 0)
	return t.file.Write(p)
}

func (l League) Find(name string) *Player {
	for i, player := range l {
		// 循环拿到的player是League的副本,我们希望拿到正本
		if player.Name == name {
			return &l[i]
		}
	}
	return nil
}

func (i *InMemoryPlayerStore) GetPlayerScore(name string) int {
	return i.score[name]
}

func (i *InMemoryPlayerStore) ProcessWin(name string) {
	i.score[name]++
}

func (i *InMemoryPlayerStore) GetLeague() League {
	var players []Player
	for k, v := range i.score {
		players = append(players, Player{k, v})
	}
	return players
}

func (f *FileSystemStore) GetLeague() League {
	return f.league
}

func (f *FileSystemStore) GetPlayerScore(name string) int {
	league := f.GetLeague()
	player := league.Find(name)
	if player != nil {
		return player.Score
	}
	return 0
}

func (f *FileSystemStore) ProcessWin(name string) {
	league := f.GetLeague()
	player := league.Find(name)
	// in case name not exists
	if player != nil {
		player.Score++
	} else {
		f.league = append(league, Player{name, 1})
	}
	f.database.Encode(league)

}

func NewPlayerServer(store PlayerStore) *PlayerServer {
	p := &PlayerServer{
		store,
		http.NewServeMux(),
	}
	p.router.HandleFunc("/hello/", p.helloHandler)
	p.router.HandleFunc("/player/", p.playerHandler)
	p.router.HandleFunc("/league", p.leagueHandler)
	return p
}

func (p PlayerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	p.router.ServeHTTP(w, r)
}

func (p PlayerServer) leagueHandler(w http.ResponseWriter, r *http.Request) {
	league := p.store.GetLeague()
	// sort by Score desc
	sort.Slice(league, func(i, j int) bool {
		return league[i].Score > league[j].Score
	})
	json.NewEncoder(w).Encode(league)
}

func (p PlayerServer) helloHandler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	who := r.URL.Path[len("/hello/"):]
	if who == "" {
		fmt.Fprint(w, "Hello World")
	} else {
		fmt.Fprint(w, "Hello "+who)
	}
}

func (p PlayerServer) playerHandler(w http.ResponseWriter, r *http.Request) {
	player := r.URL.Path[len("/player/"):]

	switch r.Method {
	case http.MethodGet:
		score := p.store.GetPlayerScore(player)
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, score)
	case http.MethodPost:
		p.store.ProcessWin(player)
		w.WriteHeader(http.StatusOK)
	}
}

func initialFileSystemStore(file *os.File) error {
	file.Seek(0, 0)
	info, err := file.Stat()
	if err != nil {
		return fmt.Errorf("problem getting file info from file %s, %v", file.Name(), err)

	}
	if info.Size() == 0 {
		file.Write([]byte("[]"))
		file.Seek(0, 0)
	}
	return nil
}

func NewFileSystemStore(file *os.File) (*FileSystemStore, error) {
	err := initialFileSystemStore(file)
	if err != nil {
		return nil, fmt.Errorf("problem getting file info from file %s, %v", file.Name(), err)

	}

	league, _ := NewLeague(file)
	return &FileSystemStore{
		database: json.NewEncoder(&Tape{file}),
		league:   league,
	}, nil
}

func NewLeague(file *os.File) (League, error) {
	file.Seek(0, 0)
	var league League
	err := json.NewDecoder(file).Decode(&league)
	if err != nil {
		return nil, err
	}
	return league, nil
}

func main() {
	//server := NewPlayerServer(&InMemoryPlayerStore{map[string]int{}})
	dbFileName := "player.db.json"
	db, err := os.OpenFile(dbFileName, os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		log.Fatalf("problem opening %s %v", dbFileName, err)
	}
	store, _ := NewFileSystemStore(db)
	server := NewPlayerServer(store)
	if err := http.ListenAndServe(":8000", server); err != nil {
		log.Fatalf("cannot startup and listen to port 8080.%#v", err)
	}
}
